{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "9f5aae62",
   "metadata": {},
   "source": [
    "## add_url_rule与app.route"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6e14e536",
   "metadata": {},
   "source": [
    "![](03_Flask视图_images/1.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "51edb9d1",
   "metadata": {},
   "source": [
    "**add_url_rule( rule, endpoint=None, view_func=None)**  这个方法用来添加url与视图函数的映射。\n",
    "\n",
    "如果没有填写 endpoint ，那么默认会使用 view_func 的名字作为 endpoint \n",
    "\n",
    "以后在使用 url_for 的时候，就要看在映射的时候有没有传递 endpoint 参数，如果传递了，那么就应该使用 endpoint 指定的字符串，如果没有传递，那么就应该使用 view_func 的名字。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "b034572a",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-10-15T03:24:50.877700Z",
     "start_time": "2023-10-15T03:24:40.354027Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " * Serving Flask app \"__main__\" (lazy loading)\n",
      " * Environment: production\n",
      "\u001b[31m   WARNING: This is a development server. Do not use it in a production deployment.\u001b[0m\n",
      "\u001b[2m   Use a production WSGI server instead.\u001b[0m\n",
      " * Debug mode: off\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n",
      "127.0.0.1 - - [15/Oct/2023 11:24:44] \"GET / HTTP/1.1\" 200 -\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "/show_me\n",
      "/\n"
     ]
    }
   ],
   "source": [
    "from flask import Response\n",
    "from flask import Flask, url_for\n",
    "\n",
    "app = Flask(__name__)\n",
    "\n",
    "# @app.route 底层就是使用的 add_url_rule\n",
    "\n",
    "\n",
    "@app.route('/', endpoint='index1')\n",
    "def index():\n",
    "    \n",
    "    # url_for中，传递的是 endpoint值，如果没有，才传递函数名\n",
    "    print(url_for('show'))\n",
    "    print(url_for('index1'))\n",
    "    return \"Hello\"\n",
    "\n",
    "\n",
    "def show_me():\n",
    "    return \"这个介绍信息！！\"\n",
    "\n",
    "\n",
    "# 如果 endpoint没有设置，url_for中就写函数的名字，如果设置了，就写endpoint的值\n",
    "app.add_url_rule('/show_me', view_func=show_me, endpoint='show')\n",
    "\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    app.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7722178d",
   "metadata": {},
   "source": [
    "## 类视图\n",
    "\n",
    "![](03_Flask视图_images/2.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aba15c94",
   "metadata": {},
   "source": [
    "之前我们接触的视图都是函数，所以一般简称函数视图。\n",
    "\n",
    "其实视图也可以基于类来实现，类视图的好处是支持继承，\n",
    "\n",
    "但是类视图不能跟函数视图一样，写完类视图还需要通过 app.add_url_rule(url_rule,view_func) 来进行注册"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0b0de1eb",
   "metadata": {},
   "source": [
    "**标准类视图使用步骤：**\n",
    "1. 标准类视图，必须继承自 flask.views.View\n",
    "\n",
    "\n",
    "2. 必须实现 dispatch_request 方法，以后请求过来后，都会执行这个方法。这个方法的返回值就相当于是之前的视图函数一样。也必须返回Response 或者子类的对象，或者是字符串，或者是元组。\n",
    "\n",
    "\n",
    "3. 必须通过 app.add_url_rule(rule,endpoint,view_func) 来做url与视图的映射。view_func 这个参数，需要使用类视图下的 as_view 类方法类转换：ListView.as_view('list') \n",
    "\n",
    "\n",
    "4. 如果指定了 endpoint ，那么在使用 url_for 反转的时候就必须使用endpoint 指定的那个值。如果没有指定 endpoint ，那么就可以使用as_view(视图名字) 中指定的视图名字来作为反转。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "03b4238f",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-10-15T03:32:15.303145Z",
     "start_time": "2023-10-15T03:32:03.534693Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " * Serving Flask app \"__main__\" (lazy loading)\n",
      " * Environment: production\n",
      "\u001b[31m   WARNING: This is a development server. Do not use it in a production deployment.\u001b[0m\n",
      "\u001b[2m   Use a production WSGI server instead.\u001b[0m\n",
      " * Debug mode: off\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n",
      "127.0.0.1 - - [15/Oct/2023 11:32:05] \"GET / HTTP/1.1\" 200 -\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "/list\n"
     ]
    }
   ],
   "source": [
    "from flask import Flask, url_for\n",
    "from flask.views import View\n",
    "app = Flask(__name__)\n",
    "\n",
    "\n",
    "@app.route('/')\n",
    "def index():\n",
    "    \n",
    "    # 打印类视图的地址\n",
    "    print(url_for('my'))\n",
    "    return 'Hello'\n",
    "\n",
    "\n",
    "class ListView(View):\n",
    "    \"\"\"创建类视图\"\"\"\n",
    "\n",
    "    def dispatch_request(self):\n",
    "        \"\"\"必须实现该方法\"\"\"\n",
    "\n",
    "        return '返回了一个List的内容！！'\n",
    "\n",
    "\n",
    "app.add_url_rule('/list', # 指定 url地址\n",
    "                 endpoint='my', # 用 url_for查询地址时，需要用到\n",
    "                 view_func=ListView.as_view('mylist'))\n",
    "\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    app.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4feea19b",
   "metadata": {},
   "source": [
    "## 类视图的好处"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "84ace301",
   "metadata": {},
   "source": [
    "![](03_Flask视图_images/3.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "98e070ac",
   "metadata": {},
   "source": [
    " 1. 可以继承，把一些共性的东西抽取出来放到父视图中，子视图直接拿来用就可以了。\n",
    " \n",
    " \n",
    " 2. 但是也不是说所有的视图都要使用类视图，这个要根据情况而定。视图函数用得最多。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "025a266a",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-10-15T03:38:04.256671Z",
     "start_time": "2023-10-15T03:37:38.930304Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " * Serving Flask app \"__main__\" (lazy loading)\n",
      " * Environment: production\n",
      "\u001b[31m   WARNING: This is a development server. Do not use it in a production deployment.\u001b[0m\n",
      "\u001b[2m   Use a production WSGI server instead.\u001b[0m\n",
      " * Debug mode: off\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n",
      "127.0.0.1 - - [15/Oct/2023 11:37:40] \"GET / HTTP/1.1\" 404 -\n",
      "127.0.0.1 - - [15/Oct/2023 11:37:51] \"GET /json2 HTTP/1.1\" 200 -\n",
      "127.0.0.1 - - [15/Oct/2023 11:37:52] \"GET /json2 HTTP/1.1\" 200 -\n",
      "127.0.0.1 - - [15/Oct/2023 11:37:59] \"GET /json2 HTTP/1.1\" 200 -\n",
      "127.0.0.1 - - [15/Oct/2023 11:37:59] \"GET /json HTTP/1.1\" 200 -\n"
     ]
    }
   ],
   "source": [
    "from flask import Flask, jsonify\n",
    "from flask.views import View\n",
    "app = Flask(__name__)\n",
    "\n",
    "\n",
    "class BaseView(View):\n",
    "    \"\"\"父视图\"\"\"\n",
    "    \n",
    "    def get_data(self):\n",
    "        \"\"\"如果子类未实现该方法，则抛出异常\"\"\"\n",
    "        raise NotImplementedError\n",
    "\n",
    "    def dispatch_request(self):\n",
    "        \"\"\"类视图必须实现该方法\"\"\"\n",
    "        \n",
    "        # 返回json数据\n",
    "        return jsonify(self.get_data())\n",
    "\n",
    "\n",
    "class JsonView(BaseView):\n",
    "    \"\"\"子视图1\"\"\"\n",
    "    def get_data(self):\n",
    "        return {'uname': '吕布', 'age': 20}\n",
    "\n",
    "\n",
    "class Json2View(BaseView):\n",
    "    \"\"\"子视图2\"\"\"\n",
    "    def get_data(self):\n",
    "        return [\n",
    "            {'name': '北理工', 'lua': 'Python'},\n",
    "            {'name': '程序员', 'lua': 'Python'},\n",
    "        ]\n",
    "\n",
    "# 建立 视图 与 URL 的联系\n",
    "app.add_url_rule('/base', view_func=BaseView.as_view('base'))\n",
    "app.add_url_rule('/json', view_func=JsonView.as_view('json'))\n",
    "app.add_url_rule('/json2', view_func=Json2View.as_view('json2'))\n",
    "\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    app.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9d8937d4",
   "metadata": {},
   "source": [
    "## 类视图实战"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "9e11753c",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-10-15T03:45:21.477827Z",
     "start_time": "2023-10-15T03:45:06.381076Z"
    },
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " * Serving Flask app \"__main__\" (lazy loading)\n",
      " * Environment: production\n",
      "\u001b[31m   WARNING: This is a development server. Do not use it in a production deployment.\u001b[0m\n",
      "\u001b[2m   Use a production WSGI server instead.\u001b[0m\n",
      " * Debug mode: off\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n",
      "127.0.0.1 - - [15/Oct/2023 11:45:07] \"GET / HTTP/1.1\" 404 -\n",
      "127.0.0.1 - - [15/Oct/2023 11:45:13] \"GET /login/ HTTP/1.1\" 200 -\n"
     ]
    }
   ],
   "source": [
    "from flask import Flask, render_template\n",
    "from flask.views import View\n",
    "\n",
    "\n",
    "app = Flask(__name__)\n",
    "\n",
    "\n",
    "class BaseView(View):\n",
    "    \"\"\"父视图\"\"\"\n",
    "    def __init__(self):\n",
    "        self.msg = {\n",
    "            'main': '课程又更新了！'\n",
    "        }\n",
    "\n",
    "\n",
    "class LoginView(BaseView):\n",
    "    \"\"\"登录子视图\"\"\"\n",
    "    def dispatch_request(self):\n",
    "        \n",
    "        # 添加数据进字典\n",
    "        my_msg = '神奇的登录功能'\n",
    "        self.msg['my_msg'] = my_msg\n",
    "        \n",
    "        # 返回登录界面\n",
    "        return render_template('类视图login.html', **self.msg)\n",
    "\n",
    "\n",
    "class RegisterView(BaseView):\n",
    "    \"\"\"注册子视图\"\"\"\n",
    "    def dispatch_request(self):\n",
    "        \n",
    "        self.msg['my_msg'] = '快捷的注册功能'\n",
    "        \n",
    "        return render_template('类视图register.html', **self.msg)\n",
    "\n",
    "# 视图与URL进行绑定\n",
    "app.add_url_rule('/login/', view_func=LoginView.as_view('login'))\n",
    "app.add_url_rule('/register/', view_func=RegisterView.as_view('register'))\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    app.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cd60cc87",
   "metadata": {},
   "source": [
    "## 基于调度方法的类视图"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4996c8ac",
   "metadata": {},
   "source": [
    "![](03_Flask视图_images/4.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5289e996",
   "metadata": {},
   "source": [
    "1. 基于方法的类视图，是根据请求的 method 来执行不同的方法的。如果用户是发送的 get 请求，那么将会执行这个类的 get 方法。如果用户发送的是 post 请求，那么将会执行这个类的 post 方法。其他的method类似，比如 delete 、 put\n",
    "\n",
    "\n",
    "2. 这种方式，可以让代码更加简洁。所有和 get 请求相关的代码都放在 get 方法中，所有和 post 请求相关的代码都放在 post 方法中。就不需要跟之前的函数一样，通过 request.method == 'GET'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "9145e87e",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-10-15T03:54:12.455499Z",
     "start_time": "2023-10-15T03:53:52.081448Z"
    },
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " * Serving Flask app \"__main__\" (lazy loading)\n",
      " * Environment: production\n",
      "\u001b[31m   WARNING: This is a development server. Do not use it in a production deployment.\u001b[0m\n",
      "\u001b[2m   Use a production WSGI server instead.\u001b[0m\n",
      " * Debug mode: off\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n",
      "127.0.0.1 - - [15/Oct/2023 11:53:53] \"GET / HTTP/1.1\" 404 -\n",
      "127.0.0.1 - - [15/Oct/2023 11:53:56] \"GET /login/ HTTP/1.1\" 404 -\n",
      "127.0.0.1 - - [15/Oct/2023 11:53:58] \"GET /login2/ HTTP/1.1\" 200 -\n",
      "127.0.0.1 - - [15/Oct/2023 11:54:04] \"POST /login2/ HTTP/1.1\" 200 -\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "zs\n",
      "123\n"
     ]
    }
   ],
   "source": [
    "from flask import Flask, request, render_template\n",
    "from flask.views import MethodView\n",
    "\n",
    "app = Flask(__name__)\n",
    "\n",
    "\n",
    "@app.route('/login1/', methods=['GET', 'POST'])\n",
    "def login():\n",
    "    \"\"\"函数视图处理不同的请求\"\"\"\n",
    "    if request.method == 'GET':\n",
    "        \n",
    "        return render_template('调度方法login.html')\n",
    "    \n",
    "    elif request.method == 'POST':\n",
    "        \n",
    "        uname = request.form.get('uname')\n",
    "        pwd = request.form.get('pwd')\n",
    "        \n",
    "        print(uname)\n",
    "        print(pwd)\n",
    "        \n",
    "        if uname == 'zs' and pwd == '123':\n",
    "            return '登录成功！'\n",
    "        \n",
    "        else:\n",
    "            return render_template('调度方法login.html')\n",
    "\n",
    "\n",
    "class LoginView(MethodView):\n",
    "    \"\"\"类视图\"\"\"\n",
    "    \n",
    "    def get(self):\n",
    "        \"\"\"处理GET请求\"\"\"\n",
    "        return render_template('调度方法login.html')\n",
    "\n",
    "    def post(self):\n",
    "        \"\"\"处理POST请求\"\"\"\n",
    "        \n",
    "        uname = request.form.get('uname')\n",
    "        pwd = request.form.get('pwd')\n",
    "        print(uname)\n",
    "        print(pwd)\n",
    "        \n",
    "        if uname == 'zs' and pwd == '123':\n",
    "            return '登录成功！'\n",
    "        else:\n",
    "            return render_template('调度方法login.html', msg='用户名或密码错误')\n",
    "\n",
    "\n",
    "app.add_url_rule('/login2/', view_func=LoginView.as_view('login2'))\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    app.run()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "7ebf027e",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-10-15T04:02:21.054126Z",
     "start_time": "2023-10-15T04:01:55.833067Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " * Serving Flask app \"__main__\" (lazy loading)\n",
      " * Environment: production\n",
      "\u001b[31m   WARNING: This is a development server. Do not use it in a production deployment.\u001b[0m\n",
      "\u001b[2m   Use a production WSGI server instead.\u001b[0m\n",
      " * Debug mode: off\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n",
      "127.0.0.1 - - [15/Oct/2023 12:01:57] \"GET / HTTP/1.1\" 404 -\n",
      "127.0.0.1 - - [15/Oct/2023 12:02:01] \"GET /login/ HTTP/1.1\" 200 -\n",
      "127.0.0.1 - - [15/Oct/2023 12:02:13] \"POST /login/ HTTP/1.1\" 200 -\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "zs\n",
      "123\n"
     ]
    }
   ],
   "source": [
    "\"\"\"基于调度方法的类视图\"\"\"\n",
    "from flask import Flask, request, render_template\n",
    "from flask.views import MethodView\n",
    "\n",
    "app = Flask(__name__)\n",
    "\n",
    "\n",
    "class LoginView(MethodView):\n",
    "    def _jump(self, msg=None):\n",
    "        \"\"\"处理 GET、POST请求的通用部分\"\"\"\n",
    "        \n",
    "        return render_template('调度方法优化login.html', msg=msg)\n",
    "\n",
    "    def get(self, msg=None):\n",
    "        return self._jump()\n",
    "\n",
    "    def post(self):\n",
    "        uname = request.form.get('uname')\n",
    "        pwd = request.form.get('pwd')\n",
    "        print(uname)\n",
    "        print(pwd)\n",
    "        if uname == 'zs' and pwd == '123':\n",
    "            return '登录成功！'\n",
    "        else:\n",
    "            return self._jump(msg='登录的用户名或密码错误')\n",
    "\n",
    "\n",
    "app.add_url_rule('/login/', view_func=LoginView.as_view('login'))\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    app.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e97d638c",
   "metadata": {},
   "source": [
    "## 装饰器"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "555c8e55",
   "metadata": {},
   "source": [
    "![](03_Flask视图_images/5.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "42d80cd5",
   "metadata": {},
   "source": [
    "python装饰器就是用于拓展原来函数功能的一种函数，这个函数的特殊之处在于它的返回值也是一个函数，\n",
    "\n",
    "使用python装饰器的好处就是在不用更改原函数的代码前提下给函数增加新的功能。\n",
    "\n",
    "在视图函数中使用自定义装饰器，那么自己定义的装饰器必须放在 app.route 下面。否则这个装饰器就起不到任何作用。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "28836b4c",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-10-15T09:12:49.519209Z",
     "start_time": "2023-10-15T09:12:08.254759Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " * Serving Flask app \"__main__\" (lazy loading)\n",
      " * Environment: production\n",
      "\u001b[31m   WARNING: This is a development server. Do not use it in a production deployment.\u001b[0m\n",
      "\u001b[2m   Use a production WSGI server instead.\u001b[0m\n",
      " * Debug mode: off\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n",
      "INFO:werkzeug: * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n",
      "INFO:root:None：尝试登录失败！！\n",
      "127.0.0.1 - - [15/Oct/2023 17:12:12] \"GET / HTTP/1.1\" 200 -\n",
      "INFO:werkzeug:127.0.0.1 - - [15/Oct/2023 17:12:12] \"GET / HTTP/1.1\" 200 -\n",
      "INFO:root:None：尝试登录失败！！\n",
      "127.0.0.1 - - [15/Oct/2023 17:12:18] \"GET / HTTP/1.1\" 200 -\n",
      "INFO:werkzeug:127.0.0.1 - - [15/Oct/2023 17:12:18] \"GET / HTTP/1.1\" 200 -\n",
      "INFO:root:这个测试日志信息！！！\n",
      "INFO:root:None：尝试登录失败！！\n",
      "127.0.0.1 - - [15/Oct/2023 17:12:19] \"GET /login/ HTTP/1.1\" 200 -\n",
      "INFO:werkzeug:127.0.0.1 - - [15/Oct/2023 17:12:19] \"GET /login/ HTTP/1.1\" 200 -\n",
      "127.0.0.1 - - [15/Oct/2023 17:12:21] \"GET /login HTTP/1.1\" 308 -\n",
      "INFO:werkzeug:127.0.0.1 - - [15/Oct/2023 17:12:21] \"\u001b[32mGET /login HTTP/1.1\u001b[0m\" 308 -\n",
      "INFO:root:这个测试日志信息！！！\n",
      "INFO:root:None：尝试登录失败！！\n",
      "127.0.0.1 - - [15/Oct/2023 17:12:21] \"GET /login/ HTTP/1.1\" 200 -\n",
      "INFO:werkzeug:127.0.0.1 - - [15/Oct/2023 17:12:21] \"GET /login/ HTTP/1.1\" 200 -\n"
     ]
    }
   ],
   "source": [
    "from flask import Flask, render_template, request\n",
    "from flask.views import MethodView\n",
    "\n",
    "from functools import wraps\n",
    "import logging\n",
    "\n",
    "# 设置日志模块\n",
    "logging.basicConfig(level=logging.INFO)\n",
    "\n",
    "# 创建 APP对象\n",
    "app = Flask(__name__)\n",
    "\n",
    "\n",
    "def login_required(func):\n",
    "    @wraps(func)\n",
    "    def wrapper(*args, **kwargs):\n",
    "\n",
    "        # 提取信息\n",
    "        uname = request.args.get('uname')\n",
    "        pwd = request.args.get('pwd')\n",
    "        \n",
    "        # 判断\n",
    "        if uname == 'zs' and pwd == '123':\n",
    "            logging.info(f'{uname}：登录成功！！')\n",
    "            return func(*args, **kwargs)\n",
    "        else:\n",
    "            logging.info(f'{uname}：尝试登录失败！！')\n",
    "            return '用户名或密码错误！'\n",
    "        \n",
    "    return wrapper\n",
    "\n",
    "\n",
    "def logger(func):\n",
    "    @wraps(func)\n",
    "    def wrapper(*args, **kwargs):\n",
    "        logging.info('这个测试日志信息！！！')\n",
    "        return func(*args, **kwargs)\n",
    "\n",
    "    return wrapper\n",
    "\n",
    "# 函数视图\n",
    "@app.route('/')\n",
    "@login_required  # index = login_required(index)\n",
    "def index():\n",
    "    return 'Hello'\n",
    "\n",
    "\n",
    "class LoginView(MethodView):\n",
    "    \"\"\"类视图\"\"\"\n",
    "    \n",
    "    # 在类视图中使用装饰器,需要重写类视图的一个类属性 decorators，\n",
    "    # 这个类属性是一个列表或者元组都可以，里面装的就是所有的装饰器。\n",
    "    decorators = [login_required, logger]\n",
    "\n",
    "    def get(self):\n",
    "        return 'Hello'\n",
    "\n",
    "# 关联类视图 与 URL\n",
    "app.add_url_rule('/login/', view_func=LoginView.as_view('login'))\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    app.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "570f4d57",
   "metadata": {},
   "source": [
    "## 蓝图介绍"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d55f9f8c",
   "metadata": {},
   "source": [
    "![](03_Flask视图_images/6.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fe99d771",
   "metadata": {},
   "source": [
    "在Flask中，使用蓝图 Blueprint 来分模块组织管理。蓝图实际可以理解为是存储一组视图方法的容器对象，其具有如下特点：\n",
    "- 一个应用可以具有多个Blueprint\n",
    "- 可以将一个Blueprint注册到任何一个未使用的URL下比如 “/user” 、 “/goods”\n",
    "- Blueprint可以单独具有自己的模板、静态文件或者其它的通用操作方法，它并不是必须要实现应用的视图和函数的\n",
    "- 在一个应用初始化时，就应该要注册需要使用的Blueprint\n",
    "\n",
    "**注意:** Blueprint并不是一个完整的应用，它不能独立于应用运行，而必须要注册到某一个应用中"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "5c6ab7c4",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-10-15T09:17:53.737368Z",
     "start_time": "2023-10-15T09:17:19.423640Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " * Serving Flask app \"__main__\" (lazy loading)\n",
      " * Environment: production\n",
      "\u001b[31m   WARNING: This is a development server. Do not use it in a production deployment.\u001b[0m\n",
      "\u001b[2m   Use a production WSGI server instead.\u001b[0m\n",
      " * Debug mode: off\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n",
      "INFO:werkzeug: * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n",
      "127.0.0.1 - - [15/Oct/2023 17:17:24] \"GET / HTTP/1.1\" 200 -\n",
      "INFO:werkzeug:127.0.0.1 - - [15/Oct/2023 17:17:24] \"GET / HTTP/1.1\" 200 -\n",
      "127.0.0.1 - - [15/Oct/2023 17:17:34] \"GET / HTTP/1.1\" 200 -\n",
      "INFO:werkzeug:127.0.0.1 - - [15/Oct/2023 17:17:34] \"GET / HTTP/1.1\" 200 -\n",
      "127.0.0.1 - - [15/Oct/2023 17:17:36] \"GET /item HTTP/1.1\" 308 -\n",
      "INFO:werkzeug:127.0.0.1 - - [15/Oct/2023 17:17:36] \"\u001b[32mGET /item HTTP/1.1\u001b[0m\" 308 -\n",
      "127.0.0.1 - - [15/Oct/2023 17:17:36] \"GET /item/ HTTP/1.1\" 200 -\n",
      "INFO:werkzeug:127.0.0.1 - - [15/Oct/2023 17:17:36] \"GET /item/ HTTP/1.1\" 200 -\n",
      "127.0.0.1 - - [15/Oct/2023 17:17:42] \"GET / HTTP/1.1\" 200 -\n",
      "INFO:werkzeug:127.0.0.1 - - [15/Oct/2023 17:17:42] \"GET / HTTP/1.1\" 200 -\n",
      "127.0.0.1 - - [15/Oct/2023 17:17:43] \"GET /user_login/ HTTP/1.1\" 404 -\n",
      "INFO:werkzeug:127.0.0.1 - - [15/Oct/2023 17:17:43] \"\u001b[33mGET /user_login/ HTTP/1.1\u001b[0m\" 404 -\n",
      "127.0.0.1 - - [15/Oct/2023 17:17:47] \"GET /user/login HTTP/1.1\" 308 -\n",
      "INFO:werkzeug:127.0.0.1 - - [15/Oct/2023 17:17:47] \"\u001b[32mGET /user/login HTTP/1.1\u001b[0m\" 308 -\n",
      "127.0.0.1 - - [15/Oct/2023 17:17:47] \"GET /user/login/ HTTP/1.1\" 200 -\n",
      "INFO:werkzeug:127.0.0.1 - - [15/Oct/2023 17:17:47] \"GET /user/login/ HTTP/1.1\" 200 -\n"
     ]
    }
   ],
   "source": [
    "from flask import Flask, Blueprint\n",
    "\n",
    "\n",
    "app = Flask(__name__)\n",
    "\n",
    "\n",
    "@app.route('/')\n",
    "def index():\n",
    "    return 'Hello'\n",
    "\n",
    "\n",
    "# 1、创建蓝图\n",
    "user_bp = Blueprint('user', __name__)\n",
    "\n",
    "# 2、管理的子功能\n",
    "\n",
    "\n",
    "@user_bp.route('/login/')\n",
    "def login():\n",
    "    return '登录模块'\n",
    "\n",
    "\n",
    "@user_bp.route('/register/')\n",
    "def register():\n",
    "    return '注册模块'\n",
    "\n",
    "\n",
    "# 1、创建蓝图\n",
    "item_bp = Blueprint('item', __name__)\n",
    "\n",
    "# 2、管理的子功能\n",
    "\n",
    "\n",
    "@item_bp.route('/item/')\n",
    "def item():\n",
    "    return '产品模块'\n",
    "\n",
    "\n",
    "# 注册蓝图 -- 将蓝图与APP关联起来\n",
    "app.register_blueprint(user_bp, \n",
    "                       url_prefix='/user') # 设置蓝图的 URL\n",
    "\n",
    "app.register_blueprint(item_bp)\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    app.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "23bce5f9",
   "metadata": {},
   "source": [
    "## 蓝图的目录结构"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "daedcb3c",
   "metadata": {},
   "source": [
    "![](03_Flask视图_images/7.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8129a6a9",
   "metadata": {},
   "source": [
    "为了让项目代码更加清晰，可以通过将代码分在不同的文件里进行管理\n",
    "\n",
    "### 根据功能模块\n",
    "\n",
    "对于一个包含多个文件的蓝图，通常将创建蓝图对象放到Python包的 __init__.py 文件中\n",
    "\n",
    "具体见 ./Blueprint/蓝图_根据功能模块"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7bb562ad",
   "metadata": {},
   "source": [
    "![](03_Flask视图_images/8.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "63f88ce6",
   "metadata": {},
   "source": [
    "### 根据技术模块"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4c9233d0",
   "metadata": {},
   "source": [
    "具体见 ./Blueprint/蓝图_根据技术模块"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a1edf917",
   "metadata": {},
   "source": [
    "![](03_Flask视图_images/9.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a5b3cb3d",
   "metadata": {},
   "source": [
    "## 蓝图中模版文件"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9d55a1d7",
   "metadata": {},
   "source": [
    "![](03_Flask视图_images/10.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "38dce2e4",
   "metadata": {},
   "source": [
    "**寻找规则：**\n",
    "\n",
    "如果项目中的templates文件夹中有相应的模版文件，就直接使用了。\n",
    "\n",
    "如果项目中的templates文件夹中没有相应的模版文件，那么就到在定义蓝图的时候指定的路径中寻找。\n",
    "\n",
    "- 并且蓝图中指定的路径可以为相对路径，相对的是当前这个蓝图文件所在的目录\n",
    "\n",
    "![](03_Flask视图_images/11.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7b7a0d36",
   "metadata": {},
   "source": [
    "因为这个蓝图文件是在  user/view.py  ，那么就会到user这个文件夹下的user_page文件夹中寻找模版文件。\n",
    "\n",
    "**小总结：**\n",
    "- 常规：蓝图文件在查找模版文件时，会以templates为根目录进行查找\n",
    "- 也可以在 创建蓝图对象的时候， 指定 模版文件的查找路径,如下\n",
    "    - news_bp =Blueprint('news',__name__,url_prefix='/news',template_folder='news_page')\n",
    "- 只有确定templates目录下没有对应的 html文件名的时候，才会去蓝图文件指定的目录下查找，指定才会生效\n",
    "- 若templates目录下，有一个与蓝图文件指定的目录下同名的一个 html文件时，优先获取templates目录下的东西"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e8d0bccb",
   "metadata": {},
   "source": [
    "**详情见：./Blueprint/蓝图中的模版文件**"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "13a20fc6",
   "metadata": {},
   "source": [
    "## 蓝图中静态文件"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ae672bfe",
   "metadata": {},
   "source": [
    "**蓝图内部静态文件:**\n",
    "\n",
    "蓝图对象创建时不会默认注册静态目录的路由。需要我们在创建时指定 static_folder 参数。\n",
    "\n",
    "下面的示例将蓝图所在目录下的 static_admin 目录设置为静态目录\n",
    "\n",
    "![](03_Flask视图_images/12.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6b172de3",
   "metadata": {},
   "source": [
    "也可通过 static_url_path 改变访问路径\n",
    "\n",
    "![](03_Flask视图_images/13.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "02bdc4b9",
   "metadata": {},
   "source": [
    "**总结：**\n",
    "\n",
    "【掌握】查找方式1：查找静态文件时，正常情况下，会以static为根目录进行查找\n",
    "![](03_Flask视图_images/14.png)\n",
    "【了解】查找方式2：查找静态文件时，非正常情况下，需要用url_for('蓝图的名字.static'),然后会去蓝图对象在创建时指定的静态文件夹目录下 去查找静态文件\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eca1d006",
   "metadata": {},
   "source": [
    "![](03_Flask视图_images/15.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "238de1a6",
   "metadata": {},
   "source": [
    "## 蓝图url_for函数"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9f509c8f",
   "metadata": {},
   "source": [
    "如果使用蓝图，那么以后想要反转蓝图中的视图函数为url，就应该在使用url_for的时候指定这个蓝图名字。\n",
    "\n",
    "app类中、模版中、同一个蓝图类中都是如此。否则就找不到这个 endpoint"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "137dcdc0",
   "metadata": {},
   "source": [
    "**html文件中**\n",
    "![](03_Flask视图_images/16.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "400bf58f",
   "metadata": {},
   "source": [
    "**python文件中**\n",
    "\n",
    "![](03_Flask视图_images/17.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "370279b6",
   "metadata": {},
   "source": [
    "## 子域名实现"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ca460c1b",
   "metadata": {},
   "source": [
    "蓝图实现子域名：\n",
    "1. 使用蓝图技术。\n",
    "2. 在创建蓝图对象的时候，需要传递一个 subdomain 参数，来指定这个子域名的前缀\n",
    "\n",
    "![](03_Flask视图_images/18.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "179adc9c",
   "metadata": {},
   "source": [
    "3. 需要在主app文件中，需要配置app.config的SERVER_NAME参数。例如：\n",
    "\n",
    "![](03_Flask视图_images/19.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "93b7f943",
   "metadata": {},
   "source": [
    "4. 在windows: C:\\Windows\\System32\\drivers\\etc 下，找到hosts文件，然后添加域名与本机的映射。Linux: /etc/hosts域名和子域名都需要做映射\n",
    "\n",
    "![](03_Flask视图_images/20.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a7df6954",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "81025e75",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "14cf3589",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.12"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {
    "height": "calc(100% - 180px)",
    "left": "10px",
    "top": "150px",
    "width": "288px"
   },
   "toc_section_display": true,
   "toc_window_display": true
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
